/*
 * I write in my free time, for personal using
 * when i fell proud of it, i will give it to public
 * now,have nothing more to say
 */
package com.quangtin3.msg;

import com.quangtin3.utils.TunllException;
import com.quangtin3.utils.Utils;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * channel for send, receive, unpack message between tunnel and agent
 * @author quanghx
 */
public class Channel {

    Socket socket;
    DataInputStream serverIn;
    DataOutputStream serverOut;
    String host;
    int port;

    public Channel() {
    }

    public Channel(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void connect() throws TunllException {
        try {
            connect(new Socket(host, port));
        } catch (IOException e) {
            throw new TunllException(e);
        }
    }

    protected void connect(Socket socket) throws TunllException {
        try {
            this.socket = socket;

            serverIn = new DataInputStream(
                    new BufferedInputStream(socket.getInputStream()));
            serverOut = new DataOutputStream(
                    new BufferedOutputStream(socket.getOutputStream()));
        } catch (IOException e) {
            throw new TunllException(e);
        }
    }

    public void accept(ServerSocket s) throws TunllException {
        try {
            connect(s.accept());
        } catch (IOException e) {
            throw new TunllException(e);
        }
    }

    @Override
    public String toString() {
        if (socket != null) {
            return socket.toString();
        } else {
            return "no connection, socket null";
        }
    }

    public void disconnect() throws TunllException {
        try {
            serverIn = null;
            serverOut = null;
            if (socket != null) {
                socket.close();
            }
        } catch (IOException e) {
            throw new TunllException(e);
        }
    }

    /**
     * receive and unpack message from other tunnel
     * @return packed Msg object
     * @throws com.quangtin3.utils.TunllException
     */
    public Msg receive() throws TunllException {
        try {
            int length = this.readInt();
            int type = this.readInt();
            int cmd = this.readInt();
            int channel = this.readInt();
            byte[] data = new byte[length];
            serverIn.readFully(data);
            return new Msg(type, cmd, channel, data);
        } catch (IOException e) {
            throw new TunllException(e);
        }
    }

    /**
     * receive and unpack message from Agent
     * @param tunnel, this is key to identify each tunnel data     * 
     * @return
     * @throws com.quangtin3.utils.TunllException
     */
    public Msg receiveFromAgent(int tunnel) throws TunllException {
        try {
            int length = serverIn.available();
            byte[] data = new byte[length];
            int len = serverIn.read(data);

            //cut of length if receive less then expected
            if (len < data.length) {
                byte[] _data = new byte[len];
                System.arraycopy(_data, 0, data, 0, len);
                data = _data;
            }

            return new Msg(0, 0, tunnel, data);
        } catch (IOException e) {
            throw new TunllException(e);
        }
    }

    /**
     * send message to other end tunnel
     * @param msg
     * @throws com.quangtin3.utils.TunllException
     */
    public void send(Msg msg) throws TunllException {
        byte[] data = msg.pack();
        try {
            serverOut.write(data, 0, data.length);
        } catch (IOException e) {
            throw new TunllException(e);
        }
    }

    /**
     * send message to Agent, cut off control data
     * @param msg
     * @throws com.quangtin3.utils.TunllException
     */
    public void sendToAgent(Msg msg) throws TunllException {
        byte[] data = msg.getData();
        try {
            serverOut.write(data, 0, data.length);
        } catch (IOException e) {
            throw new TunllException(e);
        }
    }

    /**
     * read four bytes and caculate value
     * @return
     * @throws com.quangtin3.utils.TunllException
     */
    private int readInt() throws TunllException {
        try {
            byte[] b = new byte[4];
            serverIn.read(b);
            return Utils.getInt(b);
        } catch (Exception e) {
            throw new TunllException(e);
        }
    }
}
